package com.taobao.tddl.dbsync.binlog;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.taobao.tddl.dbsync.binlog.event.LogHeader;

/**
 * Binary log event definitions. This includes generic code common to all types
 * of log events, as well as specific code for each type of log event.
 * 
 * - All numbers, whether they are 16-, 24-, 32-, or 64-bit numbers, are stored
 * in little endian, i.e., the least significant byte first, unless otherwise
 * specified.
 * 
 * representation of unsigned integers, called Packed Integer. A Packed Integer
 * has the capacity of storing up to 8-byte integers, while small integers still
 * can use 1, 3, or 4 bytes. The value of the first byte determines how to read
 * the number, according to the following table:
 * 
 * <table>
 * <caption>Format of Packed Integer</caption>
 * 
 * <tr>
 * <th>First byte</th>
 * <th>Format</th>
 * </tr>
 * 
 * <tr>
 * <td>0-250</td>
 * <td>The first byte is the number (in the range 0-250), and no more bytes are
 * used.</td>
 * </tr>
 * 
 * <tr>
 * <td>252</td>
 * <td>Two more bytes are used. The number is in the range 251-0xffff.</td>
 * </tr>
 * 
 * <tr>
 * <td>253</td>
 * <td>Three more bytes are used. The number is in the range 0xffff-0xffffff.</td>
 * </tr>
 * 
 * <tr>
 * <td>254</td>
 * <td>Eight more bytes are used. The number is in the range
 * 0xffffff-0xffffffffffffffff.</td>
 * </tr>
 * 
 * </table>
 * 
 * - Strings are stored in various formats. The format of each string is
 * documented separately.
 * 
 * @see mysql-5.1.60/sql/log_event.h
 * 
 * @author <a href="mailto:changyuan.lh@taobao.com">Changyuan.lh</a>
 * @version 1.0
 */
public abstract class LogEvent
{
    /*
     * 3 is MySQL 4.x; 4 is MySQL 5.0.0.
     * Compared to version 3, version 4 has:
     * - a different Start_log_event, which includes info about the binary log
     * (sizes of headers); this info is included for better compatibility if the
     * master's MySQL version is different from the slave's.
     * - all events have a unique ID (the triplet (server_id, timestamp at server
     * start, other) to be sure an event is not executed more than once in a
     * multimaster setup, example:
     *              M1
     *            /   \
     *           v     v
     *           M2    M3
     *           \     /
     *            v   v
     *              S
     * if a query is run on M1, it will arrive twice on S, so we need that S
     * remembers the last unique ID it has processed, to compare and know if the
     * event should be skipped or not. Example of ID: we already have the server id
     * (4 bytes), plus:
     * timestamp_when_the_master_started (4 bytes), a counter (a sequence number
     * which increments every time we write an event to the binlog) (3 bytes).
     * Q: how do we handle when the counter is overflowed and restarts from 0 ?
     * 
     * - Query and Load (Create or Execute) events may have a more precise
     *   timestamp (with microseconds), number of matched/affected/warnings rows
     * and fields of session variables: SQL_MODE,
     * FOREIGN_KEY_CHECKS, UNIQUE_CHECKS, SQL_AUTO_IS_NULL, the collations and
     * charsets, the PASSWORD() version (old/new/...).
     */
    public static final int    BINLOG_VERSION           = 4;

    /* Default 5.0 server version */
    public static final String SERVER_VERSION           = "5.0";

    /**
     * Event header offsets; these point to places inside the fixed header.
     */
    public static final int    EVENT_TYPE_OFFSET        = 4;
    public static final int    SERVER_ID_OFFSET         = 5;
    public static final int    EVENT_LEN_OFFSET         = 9;
    public static final int    LOG_POS_OFFSET           = 13;
    public static final int    FLAGS_OFFSET             = 17;

    /* event-specific post-header sizes */
    // where 3.23, 4.x and 5.0 agree
    public static final int    QUERY_HEADER_MINIMAL_LEN = (4 + 4 + 1 + 2);
    // where 5.0 differs: 2 for len of N-bytes vars.
    public static final int    QUERY_HEADER_LEN         = (QUERY_HEADER_MINIMAL_LEN + 2);

    /* Enumeration type for the different types of log events. */
    public static final int    UNKNOWN_EVENT            = 0;
    public static final int    START_EVENT_V3           = 1;
    public static final int    QUERY_EVENT              = 2;
    public static final int    STOP_EVENT               = 3;
    public static final int    ROTATE_EVENT             = 4;
    public static final int    INTVAR_EVENT             = 5;
    public static final int    LOAD_EVENT               = 6;
    public static final int    SLAVE_EVENT              = 7;
    public static final int    CREATE_FILE_EVENT        = 8;
    public static final int    APPEND_BLOCK_EVENT       = 9;
    public static final int    EXEC_LOAD_EVENT          = 10;
    public static final int    DELETE_FILE_EVENT        = 11;

    /**
     * NEW_LOAD_EVENT is like LOAD_EVENT except that it has a longer sql_ex,
     * allowing multibyte TERMINATED BY etc; both types share the same class
     * (Load_log_event)
     */
    public static final int    NEW_LOAD_EVENT           = 12;
    public static final int    RAND_EVENT               = 13;
    public static final int    USER_VAR_EVENT           = 14;
    public static final int    FORMAT_DESCRIPTION_EVENT = 15;
    public static final int    XID_EVENT                = 16;
    public static final int    BEGIN_LOAD_QUERY_EVENT   = 17;
    public static final int    EXECUTE_LOAD_QUERY_EVENT = 18;

    public static final int    TABLE_MAP_EVENT          = 19;

    /**
     * These event numbers were used for 5.1.0 to 5.1.15 and are therefore
     * obsolete.
     */
    public static final int    PRE_GA_WRITE_ROWS_EVENT  = 20;
    public static final int    PRE_GA_UPDATE_ROWS_EVENT = 21;
    public static final int    PRE_GA_DELETE_ROWS_EVENT = 22;

    /**
     * These event numbers are used from 5.1.16 and forward
     */
    public static final int    WRITE_ROWS_EVENT_V1      = 23;
    public static final int    UPDATE_ROWS_EVENT_V1     = 24;
    public static final int    DELETE_ROWS_EVENT_V1     = 25;

    /**
     * Something out of the ordinary happened on the master
     */
    public static final int    INCIDENT_EVENT           = 26;

    /**
     * Heartbeat event to be send by master at its idle time to ensure master's online status to slave
     */
    public static final int    HEARTBEAT_LOG_EVENT      = 27;

    /**
     * In some situations, it is necessary to send over ignorable data to the slave: data that a slave can handle in
     * case there is code for handling it, but which can be ignored if it is not recognized.
     */
    public static final int    IGNORABLE_LOG_EVENT      = 28;
    public static final int    ROWS_QUERY_LOG_EVENT     = 29;

    /** Version 2 of the Row events */
    public static final int    WRITE_ROWS_EVENT         = 30;
    public static final int    UPDATE_ROWS_EVENT        = 31;
    public static final int    DELETE_ROWS_EVENT        = 32;

    public static final int    GTID_LOG_EVENT           = 33;
    public static final int    ANONYMOUS_GTID_LOG_EVENT = 34;

    public static final int    PREVIOUS_GTIDS_LOG_EVENT = 35;
    
    // mariaDb 5.5.34
    /* New MySQL/Sun events are to be added right above this comment */
    public static final int    MYSQL_EVENTS_END         = 36;

    public static final int    MARIA_EVENTS_BEGIN       = 160;
    /* New Maria event numbers start from here */
    public static final int    ANNOTATE_ROWS_EVENT      = 160;
    /*
    Binlog checkpoint event. Used for XA crash recovery on the master, not used
    in replication.
    A binlog checkpoint event specifies a binlog file such that XA crash
    recovery can start from that file - and it is guaranteed to find all XIDs
    that are prepared in storage engines but not yet committed.
     */
    public static final int     BINLOG_CHECKPOINT_EVENT = 161;
    /*
    Gtid event. For global transaction ID, used to start a new event group,
    instead of the old BEGIN query event, and also to mark stand-alone
    events.
     */
    public static final int    GTID_EVENT               = 162;
    /*
    Gtid list event. Logged at the start of every binlog, to record the
    current replication state. This consists of the last GTID seen for
    each replication domain.
     */
    public static final int    GTID_LIST_EVENT          = 163;

    /** end marker */
    public static final int    ENUM_END_EVENT           = 164;

    /**
    1 byte length, 1 byte format
    Length is total length in bytes, including 2 byte header
    Length values 0 and 1 are currently invalid and reserved.
    */
    public static final int    EXTRA_ROW_INFO_LEN_OFFSET    = 0;
    public static final int    EXTRA_ROW_INFO_FORMAT_OFFSET = 1;
    public static final int    EXTRA_ROW_INFO_HDR_BYTES     = 2;
    public static final int    EXTRA_ROW_INFO_MAX_PAYLOAD   = (255 - EXTRA_ROW_INFO_HDR_BYTES);
    
    // Events are without checksum though its generator
    public static final int    BINLOG_CHECKSUM_ALG_OFF      = 0;
    // is checksum-capable New Master (NM).
    // CRC32 of zlib algorithm.
    public static final int    BINLOG_CHECKSUM_ALG_CRC32    = 1;
    // the cut line: valid alg range is [1, 0x7f].
    public static final int    BINLOG_CHECKSUM_ALG_ENUM_END = 2;
    // special value to tag undetermined yet checksum
    public static final int    BINLOG_CHECKSUM_ALG_UNDEF    = 255;
    // or events from checksum-unaware servers

    public static final int    CHECKSUM_CRC32_SIGNATURE_LEN = 4;
    public static final int    BINLOG_CHECKSUM_ALG_DESC_LEN = 1;
    /**
     * defined statically while there is just one alg implemented
     */
    public static final int    BINLOG_CHECKSUM_LEN          = CHECKSUM_CRC32_SIGNATURE_LEN;
    
    /* MySQL or old MariaDB slave with no announced capability. */
    public static final int    MARIA_SLAVE_CAPABILITY_UNKNOWN           = 0;

    /* MariaDB >= 5.3, which understands ANNOTATE_ROWS_EVENT. */
    public static final int    MARIA_SLAVE_CAPABILITY_ANNOTATE          = 1;
    /*
     * MariaDB >= 5.5. This version has the capability to tolerate events
     * omitted from the binlog stream without breaking replication (MySQL slaves
     * fail because they mis-compute the offsets into the master's binlog).
     */
    public static final int    MARIA_SLAVE_CAPABILITY_TOLERATE_HOLES    = 2;
    /* MariaDB >= 10.0, which knows about binlog_checkpoint_log_event. */
    public static final int    MARIA_SLAVE_CAPABILITY_BINLOG_CHECKPOINT = 3;
    /* MariaDB >= 10.0.1, which knows about global transaction id events. */
    public static final int    MARIA_SLAVE_CAPABILITY_GTID              = 4;

    /* Our capability. */
    public static final int    MARIA_SLAVE_CAPABILITY_MINE              = MARIA_SLAVE_CAPABILITY_GTID;
    
    /**
        For an event, 'e', carrying a type code, that a slave,
        's', does not recognize, 's' will check 'e' for
        LOG_EVENT_IGNORABLE_F, and if the flag is set, then 'e'
        is ignored. Otherwise, 's' acknowledges that it has
        found an unknown event in the relay log.
     */
    public static final int   LOG_EVENT_IGNORABLE_F    = 0x80;
    
    /** enum_field_types */
    public static final int    MYSQL_TYPE_DECIMAL       = 0;
    public static final int    MYSQL_TYPE_TINY          = 1;
    public static final int    MYSQL_TYPE_SHORT         = 2;
    public static final int    MYSQL_TYPE_LONG          = 3;
    public static final int    MYSQL_TYPE_FLOAT         = 4;
    public static final int    MYSQL_TYPE_DOUBLE        = 5;
    public static final int    MYSQL_TYPE_NULL          = 6;
    public static final int    MYSQL_TYPE_TIMESTAMP     = 7;
    public static final int    MYSQL_TYPE_LONGLONG      = 8;
    public static final int    MYSQL_TYPE_INT24         = 9;
    public static final int    MYSQL_TYPE_DATE          = 10;
    public static final int    MYSQL_TYPE_TIME          = 11;
    public static final int    MYSQL_TYPE_DATETIME      = 12;
    public static final int    MYSQL_TYPE_YEAR          = 13;
    public static final int    MYSQL_TYPE_NEWDATE       = 14;
    public static final int    MYSQL_TYPE_VARCHAR       = 15;
    public static final int    MYSQL_TYPE_BIT           = 16;
    public static final int    MYSQL_TYPE_TIMESTAMP2    = 17;
    public static final int    MYSQL_TYPE_DATETIME2     = 18;
    public static final int    MYSQL_TYPE_TIME2         = 19;
    public static final int    MYSQL_TYPE_NEWDECIMAL    = 246;
    public static final int    MYSQL_TYPE_ENUM          = 247;
    public static final int    MYSQL_TYPE_SET           = 248;
    public static final int    MYSQL_TYPE_TINY_BLOB     = 249;
    public static final int    MYSQL_TYPE_MEDIUM_BLOB   = 250;
    public static final int    MYSQL_TYPE_LONG_BLOB     = 251;
    public static final int    MYSQL_TYPE_BLOB          = 252;
    public static final int    MYSQL_TYPE_VAR_STRING    = 253;
    public static final int    MYSQL_TYPE_STRING        = 254;
    public static final int    MYSQL_TYPE_GEOMETRY      = 255;

    public static String getTypeName(final int type)
    {
        switch (type)
        {
        case START_EVENT_V3:
            return "Start_v3";
        case STOP_EVENT:
            return "Stop";
        case QUERY_EVENT:
            return "Query";
        case ROTATE_EVENT:
            return "Rotate";
        case INTVAR_EVENT:
            return "Intvar";
        case LOAD_EVENT:
            return "Load";
        case NEW_LOAD_EVENT:
            return "New_load";
        case SLAVE_EVENT:
            return "Slave";
        case CREATE_FILE_EVENT:
            return "Create_file";
        case APPEND_BLOCK_EVENT:
            return "Append_block";
        case DELETE_FILE_EVENT:
            return "Delete_file";
        case EXEC_LOAD_EVENT:
            return "Exec_load";
        case RAND_EVENT:
            return "RAND";
        case XID_EVENT:
            return "Xid";
        case USER_VAR_EVENT:
            return "User var";
        case FORMAT_DESCRIPTION_EVENT:
            return "Format_desc";
        case TABLE_MAP_EVENT:
            return "Table_map";
        case PRE_GA_WRITE_ROWS_EVENT:
            return "Write_rows_event_old";
        case PRE_GA_UPDATE_ROWS_EVENT:
            return "Update_rows_event_old";
        case PRE_GA_DELETE_ROWS_EVENT:
            return "Delete_rows_event_old";
        case WRITE_ROWS_EVENT_V1:
            return "Write_rows_v1";
        case UPDATE_ROWS_EVENT_V1:
            return "Update_rows_v1";
        case DELETE_ROWS_EVENT_V1:
            return "Delete_rows_v1";
        case BEGIN_LOAD_QUERY_EVENT:
            return "Begin_load_query";
        case EXECUTE_LOAD_QUERY_EVENT:
            return "Execute_load_query";
        case INCIDENT_EVENT:
            return "Incident";
        case HEARTBEAT_LOG_EVENT:
            return "Heartbeat";
        case IGNORABLE_LOG_EVENT:
            return "Ignorable";
        case ROWS_QUERY_LOG_EVENT:
            return "Rows_query";
        case WRITE_ROWS_EVENT:
            return "Write_rows";
        case UPDATE_ROWS_EVENT:
            return "Update_rows";
        case DELETE_ROWS_EVENT:
            return "Delete_rows";
        case GTID_LOG_EVENT:
            return "Gtid";
        case ANONYMOUS_GTID_LOG_EVENT:
            return "Anonymous_Gtid";
        case PREVIOUS_GTIDS_LOG_EVENT:
            return "Previous_gtids";
        default:
            return "Unknown"; /* impossible */
        }
    }

    protected static final Log logger = LogFactory.getLog(LogEvent.class);

    protected final LogHeader  header;

    protected LogEvent(LogHeader header)
    {
        this.header = header;
    }

    /**
     * Return event header.
     */
    public final LogHeader getHeader()
    {
        return header;
    }

    /**
     * The total size of this event, in bytes. In other words, this is the sum
     * of the sizes of Common-Header, Post-Header, and Body.
     */
    public final int getEventLen()
    {
        return header.getEventLen();
    }

    /**
     * Server ID of the server that created the event.
     */
    public final long getServerId()
    {
        return header.getServerId();
    }

    /**
     * The position of the next event in the master binary log, in bytes from
     * the beginning of the file. In a binlog that is not a relay log, this is
     * just the position of the next event, in bytes from the beginning of the
     * file. In a relay log, this is the position of the next event in the
     * master's binlog.
     */
    public final long getLogPos()
    {
        return header.getLogPos();
    }

    /**
     * The time when the query started, in seconds since 1970.
     */
    public final long getWhen()
    {
        return header.getWhen();
    }
    
    
}
